﻿using System.Runtime.CompilerServices;
using UnityEngine.EventSystems;

namespace UnityEngine.UI
{
    [AddComponentMenu("Layout/Content Size Adjust", 141)]
    [ExecuteAlways]
    [RequireComponent(typeof(RectTransform))]
    /// <summary>
    /// Resizes a RectTransform to fit the size of its content.
    /// </summary>
    /// <remarks>
    /// The ContentSizeFitter can be used on GameObjects that have one or more ILayoutElement components, such as Text, Image, HorizontalLayoutGroup, VerticalLayoutGroup, and GridLayoutGroup.
    /// </remarks>
    public class ContentSizeAdjust : UIBehaviour, ILayoutSelfController
    {
        /// <summary>
        /// The size fit modes avaliable to use.
        /// </summary>
        public enum FitMode
        {
            /// <summary>
            /// Don't perform any resizing.
            /// </summary>
            Unconstrained,
            /// <summary>
            /// Resize to the minimum size of the content.
            /// </summary>
            MinSize,
            /// <summary>
            /// Resize to the preferred size of the content.
            /// </summary>
            PreferredSize
        }

        public enum ClampMode
        {
            NotClamp = 0,
            RelativeToParent = 1,
            Absolute = 2,
        }

        [System.Serializable]
        public struct ClampSize
        {
            [SerializeField] ClampMode clampType;
            [SerializeField] float size;

            public static float GetClampSize(RectTransform trans, int axis, float size, ClampSize min, ClampSize max)
            {
                float result = size;
                var parent = trans.parent as RectTransform;
                var parentsize = parent == null ? size : (axis == 0 ? parent.sizeDelta.x : parent.sizeDelta.y);
                if(min.clampType == ClampMode.Absolute)
                {
                    result = result < min.size ? min.size : result;
                }
                else if(min.clampType == ClampMode.RelativeToParent)
                {
                    var minsize = min.size + parentsize;
                    result = result < minsize ? minsize : result;
                }
                if(max.clampType == ClampMode.Absolute)
                {
                    result = result < max.size ? result : max.size;
                }
                else if(max.clampType == ClampMode.RelativeToParent)
                {
                    var maxsize = max.size + parentsize;
                    result = result < maxsize ? result : maxsize;
                }
                return result;
            }
        }

        [SerializeField] protected FitMode m_HorizontalFit = FitMode.Unconstrained;
        [SerializeField] protected ClampSize m_MinWidth;
        [SerializeField] protected ClampSize m_MaxWidth;
        [SerializeField] protected bool m_ExtendParentWidth;
        /// <summary>
        /// The fit mode to use to determine the width.
        /// </summary>
        public FitMode horizontalFit
        {
            get { return m_HorizontalFit; }
            set
            {
                if (value != m_HorizontalFit)
                {
                    m_HorizontalFit = value;
                    SetDirty();
                }
            }
        }

        [SerializeField] protected FitMode m_VerticalFit = FitMode.Unconstrained;
        [SerializeField] protected ClampSize m_MinHeight;
        [SerializeField] protected ClampSize m_MaxHeight;
        [SerializeField] protected bool m_ExtendParentHeight;

        [SerializeField] protected Vector2 m_BorderSize;

        /// <summary>
        /// The fit mode to use to determine the height.
        /// </summary>
        public FitMode verticalFit
        {
            get { return m_VerticalFit; }
            set
            {
                if (m_VerticalFit != value)
                {
                    m_VerticalFit = value;
                    SetDirty();
                }
            }
        }

        [System.NonSerialized] private RectTransform m_Rect;
        private RectTransform rectTransform
        {
            get
            {
                if (m_Rect == null)
                    m_Rect = GetComponent<RectTransform>();
                return m_Rect;
            }
        }

        // field is never assigned warning
        #pragma warning disable 649
        private DrivenRectTransformTracker m_Tracker;
        #pragma warning restore 649

        protected ContentSizeAdjust()
        {}

        protected override void OnEnable()
        {
            base.OnEnable();
            SetDirty();
        }

        protected override void OnDisable()
        {
            m_Tracker.Clear();
            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
            base.OnDisable();
        }

        protected override void OnRectTransformDimensionsChange()
        {
            SetDirty();
        }

        private void HandleSelfFittingAlongAxis(int axis)
        {
            FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
            if (fitting == FitMode.Unconstrained)
            {
                // Keep a reference to the tracked transform, but don't control its properties:
                m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
                return;
            }

            m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));

            // Set size to min or preferred size
            float size;
            if (fitting == FitMode.MinSize)
                size = LayoutUtility.GetMinSize(m_Rect, axis);
            else
                size = LayoutUtility.GetPreferredSize(m_Rect, axis);
            if (axis == 0)
                size = ClampSize.GetClampSize(rectTransform, axis, size, m_MinWidth, m_MaxWidth);
            else
                size = ClampSize.GetClampSize(rectTransform, axis, size, m_MinHeight, m_MaxHeight);
            rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, size);
            var extend = axis == 0 ? m_ExtendParentWidth : m_ExtendParentHeight;
            if (extend)
            {
                var trans = rectTransform.parent as RectTransform;
                if (trans != null)
                {
                    var border = axis == 0 ? m_BorderSize.x : m_BorderSize.y;
                    trans.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, size + border * 2f);
                    m_Tracker.Add(this, trans, axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY);
                }
            }
        }

        /// <summary>
        /// Calculate and apply the horizontal component of the size to the RectTransform
        /// </summary>
        public virtual void SetLayoutHorizontal()
        {
            m_Tracker.Clear();
            HandleSelfFittingAlongAxis(0);
        }

        /// <summary>
        /// Calculate and apply the vertical component of the size to the RectTransform
        /// </summary>
        public virtual void SetLayoutVertical()
        {
            HandleSelfFittingAlongAxis(1);
        }

        protected void SetDirty()
        {
            if (!IsActive())
                return;

            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
        }

    #if UNITY_EDITOR
        protected override void OnValidate()
        {
            SetDirty();
        }

    #endif
    }
}
